/*
 * Copyright (C) 2009-2010 Geometer Plus <contact@geometerplus.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package org.geometerplus.fbreader.formats.pdb;

import org.geometerplus.fbreader.bookmodel.BookModel;
import org.geometerplus.fbreader.formats.html.HtmlReader;
import org.geometerplus.fbreader.formats.html.HtmlTag;
import org.geometerplus.zlibrary.core.html.ZLByteBuffer;
import org.geometerplus.zlibrary.core.html.ZLHtmlAttributeMap;
import org.geometerplus.zlibrary.core.image.ZLFileImage;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.CharsetDecoder;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;

public class MobipocketHtmlBookReader extends HtmlReader {
    private final CharsetDecoder myTocDecoder;
    private MobipocketStream myMobipocketStream;

    MobipocketHtmlBookReader(BookModel model) throws UnsupportedEncodingException {
        super(model);
        myTocDecoder = createDecoder();
    }

    public InputStream getInputStream() throws IOException {
        myMobipocketStream = new MobipocketStream(Model.Book.File);
        return myMobipocketStream;
    }

    private boolean myReadGuide;
    private int myTocStartOffset = Integer.MAX_VALUE;
    private int myTocEndOffset = Integer.MAX_VALUE;
    private final TreeMap<Integer, String> myTocEntries = new TreeMap<Integer, String>();
    private final TreeMap<Integer, Integer> myPositionToParagraph = new TreeMap<Integer, Integer>();
    private final TreeSet<Integer> myFileposReferences = new TreeSet<Integer>();
    private int myCurrentTocPosition = -1;
    private final ZLByteBuffer myTocBuffer = new ZLByteBuffer();

    private boolean tocRangeContainsPosition(int position) {
        return (myTocStartOffset <= position) && (position < myTocEndOffset);
    }

    @Override
    public void startElementHandler(byte tag, int offset, ZLHtmlAttributeMap attributes) {
        final int paragraphIndex = Model.BookTextModel.getParagraphsNumber();
        myPositionToParagraph.put(offset, paragraphIsOpen() ? paragraphIndex - 1 : paragraphIndex);
        switch (tag) {
            case HtmlTag.IMG: {
                final ZLByteBuffer recIndex = attributes.getValue("recindex");
                if (recIndex != null) {
                    try {
                        final int index = Integer.parseInt(recIndex.toString());
                        if (paragraphIsOpen()) {
                            endParagraph();
                            addImageReference("" + index);
                            beginParagraph();
                        } else {
                            addImageReference("" + index);
                        }
                    } catch (NumberFormatException e) {
                    }
                }
                break;
            }
            case HtmlTag.GUIDE:
                myReadGuide = true;
                break;
            case HtmlTag.REFERENCE:
                if (myReadGuide) {
                    final ZLByteBuffer fp = attributes.getValue("filepos");
                    final ZLByteBuffer title = attributes.getValue("title");
                    if ((fp != null) && (title != null)) {
                        try {
                            int filePosition = Integer.parseInt(fp.toString());
                            myTocEntries.put(filePosition, title.toString(myAttributeDecoder));
                            if (tocRangeContainsPosition(filePosition)) {
                                myTocEndOffset = filePosition;
                            }
                            if (attributes.getValue("type").equalsToLCString("toc")) {
                                myTocStartOffset = filePosition;
                                final SortedMap<Integer, String> subMap =
                                        myTocEntries.tailMap(filePosition + 1);
                                if (!subMap.isEmpty()) {
                                    myTocEndOffset = subMap.firstKey();
                                }
                            }
                        } catch (NumberFormatException e) {
                        }
                    }
                }
                break;
            case HtmlTag.A: {
                final ZLByteBuffer fp = attributes.getValue("filepos");
                if (fp != null) {
                    try {
                        int filePosition = Integer.parseInt(fp.toString());
                        if (tocRangeContainsPosition(offset)) {
                            myCurrentTocPosition = filePosition;
                            if (tocRangeContainsPosition(filePosition)) {
                                myTocEndOffset = filePosition;
                            }
                        }
                        myFileposReferences.add(filePosition);
                        // TODO: add hyperlink control
                    } catch (NumberFormatException e) {
                    }
                }
                super.startElementHandler(tag, offset, attributes);
                break;
            }
            default:
                super.startElementHandler(tag, offset, attributes);
                break;
        }
    }

    @Override
    public void endElementHandler(byte tag) {
        switch (tag) {
            case HtmlTag.IMG:
                break;
            case HtmlTag.GUIDE:
                myReadGuide = false;
                break;
            case HtmlTag.REFERENCE:
                break;
            case HtmlTag.A:
                if (myCurrentTocPosition != -1) {
                    if (!myTocBuffer.isEmpty()) {
                        myTocEntries.put(myCurrentTocPosition, myTocBuffer.toString(myTocDecoder));
                        myTocBuffer.clear();
                    }
                    myCurrentTocPosition = -1;
                }
                super.endElementHandler(tag);
                break;
            default:
                super.endElementHandler(tag);
                break;
        }
    }

    @Override
    public void byteDataHandler(byte[] data, int start, int length) {
        if (myCurrentTocPosition != -1) {
            myTocBuffer.append(data, start, length);
        }
        super.addByteData(data, start, length);
    }

    @Override
    public void startDocumentHandler() {
        super.startDocumentHandler();

        for (int index = 0; ; ++index) {
            final int offset = myMobipocketStream.getImageOffset(index);
            if (offset < 0) {
                break;
            }
            final int length = myMobipocketStream.getImageLength(index);
            if (length <= 0) {
                break;
            }
            addImage("" + index, new ZLFileImage("image/auto", Model.Book.File, offset, length));
        }
    }

    @Override
    public void endDocumentHandler() {
        for (Map.Entry<Integer, String> entry : myTocEntries.entrySet()) {
            final SortedMap<Integer, Integer> subMap =
                    myPositionToParagraph.tailMap(entry.getKey());
            if (subMap.isEmpty()) {
                break;
            }
            beginContentsParagraph(subMap.get(subMap.firstKey()));
            addContentsData(entry.getValue().toCharArray());
            endContentsParagraph();
        }
        super.endDocumentHandler();
    }
}
